using System;
using UnityEngine;
using Zenject;

namespace Enemy
{
    public class Enemy : MonoBehaviour
    {
        [Header("References")]
        [SerializeField] private GameObject _shootEffect;
        [SerializeField] private AudioSource _shootSound;
        [SerializeField] private AudioSource _deathSound;
        [SerializeField] private PatrolPath[] _patrolPaths;
        [Header("Weapon")]
        [SerializeField, Min(1)] private int _damage;
        [SerializeField] private float _cooldown;
        [Header("Distance")]
        [SerializeField] private float _agroDistance;
        [SerializeField] private float _deAgroDistance;
        [SerializeField] private float _shootingDistance;
        [Header("Movement")]
        [SerializeField] private float _walkingSpeed;
        [SerializeField] private float _runningSpeed;
        [SerializeField] private float _lookTime;

        [Inject] private Player.Player _player;
        [Inject] private Managers.GameManager _manager;

        private Transform[] _patrolPoints;
        private Transform _shootPoint;
        private Transform _selfTransform;
        private Animator _animator;
        private float _time;
        private bool _isSeePlayer;
        private bool _isInvisible;
        private bool _isDied;
        private int _currentPoint;
        private static readonly int _isShootingHash
            = Animator.StringToHash("isShooting");
        private static readonly int _isRunningHash
            = Animator.StringToHash("isRunning");
        private static readonly int _deathHash = Animator.StringToHash("death");

        private void Start()
        {
            _animator = GetComponent<Animator>();
            _shootPoint = GetComponentInChildren<ShootPoint>().transform;
            _patrolPoints = Utils.Choice(_patrolPaths).Points;
            _selfTransform = transform;
            _selfTransform.position = _patrolPoints[0].position;
        }

        private void Update()
        {
            if (_player.IsDied)
                _animator.SetBool(_isRunningHash, false);

            if (_isDied || _player.IsDied)
            {
                if (_isInvisible) Destroy(gameObject);
                return;
            }

            _time += Time.deltaTime;

            if (!_isSeePlayer)
                _isSeePlayer = IsSeePlayer();

            _animator.SetBool(_isRunningHash, _isSeePlayer);
        }

        private void FixedUpdate()
        {
            if (_isDied) return;
            if (_isSeePlayer)
            {
                FollowPlayer();
                return;
            }
            Patrol();
        }

        public void Kill()
        {
            if (_isDied) return;

            _deathSound.Play();
            _animator.SetTrigger(_deathHash);
            _isDied = true;
            _manager.IncreaseScore();
            GetComponentInChildren<Pointer>().Destroy();
        }

        private void TryShoot()
        {
            if (_time < _cooldown) return;

            Instantiate(
                _shootEffect,
                _shootPoint.position,
                _shootPoint.rotation
            );
            _shootSound.Play();
            _time = 0f;

            Vector3 direction = _player.transform.position - transform.position;
            if (!Physics.Raycast(
                    _selfTransform.position,
                    direction,
                    out RaycastHit hit))
                return;
            if (!hit.transform.TryGetComponent(out Player.Player _)) return;
            _player.TakeDamage(_damage, "был застрелен)");
        }

        private void OnBecameInvisible()
        {
            _isInvisible = true;
        }

        private void OnBecameVisible()
        {
            _isInvisible = false;
        }

        private void FollowPlayer()
        {
            Vector3 selfPosition = _selfTransform.position;
            Vector3 targetTransformPosition = _player.transform.position;
            Vector3 targetPosition;
            try
            {
                targetPosition = new Vector3(
                    targetTransformPosition.x,
                    selfPosition.y,
                    targetTransformPosition.z
                );
            }
            catch (NullReferenceException)
            {
                return;
            }

            Quaternion targetRotation = Quaternion.LookRotation(
                targetPosition - selfPosition
            );

            _selfTransform.rotation = Quaternion.Slerp(
                _selfTransform.rotation,
                targetRotation,
                Time.fixedDeltaTime * _lookTime
            );

            float distance = Vector3.Distance(
                targetPosition,
                selfPosition
            );

            if (distance >= _deAgroDistance)
            {
                _isSeePlayer = false;
                return;
            }

            if (distance <= _shootingDistance)
            {
                _animator.SetBool(_isShootingHash, true);
                TryShoot();
                return;
            }
            _animator.SetBool(_isShootingHash, false);

            _selfTransform.position = Vector3.MoveTowards(
                _selfTransform.position,
                targetPosition,
                _runningSpeed
            );
        }

        private bool IsSeePlayer()
        {
            Vector3 selfPos = _selfTransform.position;
            Vector3 playerPos = _player.transform.position;

            float dist = Vector2.Distance(
                Utils.Vec3ToVec2Xz(selfPos),
                Utils.Vec3ToVec2Xz(playerPos)
                );

            if (dist < _agroDistance) return true;

            Vector3 direction = playerPos - selfPos;
            return Physics.Raycast(selfPos, direction, out RaycastHit hit)
                   && hit.transform == _player.transform;
        }

        private void Patrol()
        {
            Vector3 selfPosition = _selfTransform.position;
            Vector3 point = _patrolPoints[_currentPoint].position;
            point.y = selfPosition.y;

            if (Vector3.Distance(_selfTransform.position, point) <= 1f)
            {
                // go to next point
                _currentPoint = (_currentPoint + 1) % _patrolPoints.Length;
                return;
            }

            _selfTransform.rotation = Quaternion.LookRotation(
                point - _selfTransform.position
            );

            _selfTransform.position = Vector3.MoveTowards(
                selfPosition,
                point,
                _walkingSpeed
            );
        }
    }
}
